AWS Elemental MediaStoreでCDN認証してオリジン保護してみた
はじめに
清水です。先日、AWS Elemental MediaPackageでCDN認証を使ってオリジン保護ができるようになったアップデートをお届けしました。
AWS Media Servicesを使ってライブ配信を行う際は、要件によってオリジンを上記のジャストインタイムパッケージングサービスであるAWS Elemental MediaPackageか、メディア向けに最適化されたストレージサービスであるAWS Elemental MediaStoreかを選択することができます。ではMediaStoreでMediaPacakgeのようにCDN認証によるオリジン保護ができるのか、方法を確認し実際にMediaStoreとAmazon CloudFrontを用いてCDN認証によるオリジン保護をやってみたのでまとめてみます。
AWS Elemental MediaStoreでのCDN認証によるオリジン保護の方法
MediaStoreでのCDN認証によるオリジン保護の方法について、MediaPackageのようなCDN認証機能、というものはありませんでした。ですがMediaStoreのコンテナポリシーを利用することで、アクセスをCDNのみからというようにアクセス制御することは可能でした。イメージとしてはCloudFrontとS3連携時のOAI(Origin Access Identity)に似ているかと思います。具体的には以下となります。(なお、本エントリではCloudFrontをCDNとして扱いますが、別CDNでも同様の機能があれば実現は可能かと考えます。)
- CloudFrontのカスタムオリジンヘッダによりUser Agentを個別の値に設定
- MediaStoreではそのUser Agentの値であればアクセスを許可するようコンテナポリシーを設定
以下のフォーラムやAWS Media Blogのエントリを参考にしました。
- AWS Developer Forums: CloudFront OAI on MediaStore ...
- Using Amazon CloudFront and AWS Media Services | AWS Media Blog
では実際に設定していきます。(設定はMediaStore→CloudFrontの順で行いました。)
認証コードの作成
まずは認証コードとなる値を作成(決定)します。MediaPackageのCDN認証機能と同じく、version 4 UUIDを生成してこれを利用しました。
$ python Python 3.7.3 (default, Apr 23 2019, 11:32:00) [Clang 10.0.1 (clang-1001.0.46.4)] on darwin Type "help", "copyright", "credits" or "license" for more information. >>> import uuid >>> str(uuid.uuid4()) '995c728c-d39b-4c1c-bab6-724a77795b9d'
この995c728c-d39b-4c1c-bab6-724a77795b9d
を本エントリでは認証コードとして扱います。
MediaStoreのコンテナポリシーの設定
MediaStoreのコンテナポリシーで、認証コードをUser Agentとして持つアクセスに対し、読み取り権限を与えるよう設定します。まずはContainer作成から進めていきます。
Container作成後、詳細画面にてコンテナポリシーを設定します。Container作成直後のデフォルトのコンテナポリシーでは、以下のように作成したAWSアカウントにhttpsでのフルアクセスを許可する設定がされています。
{ "Version": "2012-10-17", "Statement": [{ "Sid": "MediaStoreFullAccess", "Action": [ "mediastore:*" ], "Principal": {"AWS" : "arn:aws:iam::123456789012:root"}, "Effect": "Allow", "Resource": "arn:aws:mediastore:ap-northeast-1:123456789012:container/MediaStoreCDNAuthorizationTestContainer/*", "Condition": { "Bool": { "aws:SecureTransport": "true" } } }] }
Edit policyで編集画面に進み、以下の内容を追加します。該当コンテナに対して、GetObject並びにDescribeObject権限を与える内容ですが、ConfitionでSecureTransport
の他、UserAgent
を指定しています。ここでCondition内はand条件となりますので、httpsアクセスでかつ、User Agentの値が認証コードであるアクセスのみが許可される、ということになります。
{ "Sid": "CloudFrontRead", "Effect": "Allow", "Principal": "*", "Action": [ "mediastore:GetObject", "mediastore:DescribeObject" ], "Resource": "arn:aws:mediastore:[リージョン]:[AWSアカウントID]:container/[コンテナ名]/*", "Condition": { "StringEquals": { "aws:UserAgent": "[認証コード]" }, "Bool": { "aws:SecureTransport": "true" } } }
実際に設定した画面がこちらです。
実際のコンテナポリシー内容は下記です。ハイライト部分が追加箇所になります。
{ "Version" : "2012-10-17", "Statement" : [ { "Sid" : "MediaStoreFullAccess", "Effect" : "Allow", "Principal" : { "AWS" : "arn:aws:iam::123456789012:root" }, "Action" : "mediastore:*", "Resource" : "arn:aws:mediastore:ap-northeast-1:123456789012:container/MediaStoreCDNAuthorizationTestContainer/*", "Condition" : { "Bool" : { "aws:SecureTransport" : "true" } } }, { "Sid" : "CloudFrontRead", "Effect" : "Allow", "Principal" : "*", "Action" : [ "mediastore:GetObject", "mediastore:DescribeObject" ], "Resource" : "arn:aws:mediastore:ap-northeast-1:123456789012:container/MediaStoreCDNAuthorizationTestContainer/*", "Condition" : { "StringEquals" : { "aws:UserAgent" : "995c728c-d39b-4c1c-bab6-724a77795b9d" }, "Bool" : { "aws:SecureTransport" : "true" } } } ] }
MediaStoreのコンテナポリシー設定は以上です。必要に応じてコンテナのCORSポリシーやライフサイクルポリシーを設定しておきましょう。
CloudFrontでUser Agentを個別の値に設定
続いてCloudFront側でカスタムオリジンヘッダを設定し、User Agentを個別の値(今回の「認証コード」)になるよう設定していきます。AWS Management ConsoleのCloudFrontのページ、ディストリビューション一覧画面から[Create Distribution]で進みます。Web distributionを作成していきましょう。
Create Distribution画面、Origin Domain NameでMediaStore Containersから先ほど作成、設定したContainerを選択します。
Origin IDなど項目が自動的に設定されますが、ここからいくつかの項目を変更していきます。まずはOrigin Protocol Policy
についてはHTTPS Only
としました。(MediaStoreのコンテナポリシーでawsSecureTransport
がtrue
の場合にしか権限を付与していないためです。(HTTPSのみアクセス許可しています。)
続いてここが実際のCDN認証部分になります、Origin Custom Headers
の箇所にて、Header Namer
をUser-Agent
、Value
を認証コードである995c728c-d39b-4c1c-bab6-724a77795b9d
と設定します。
Default Cache Behavior Settings、Distribution Settingsについては、必要に応じて設定を変更し、[Create Distribution]でディストリビューションを作成します。(CORS設定が必要な場合は、Cache Behavior SettingsでOrigin Headerを転送する設定を忘れないようにしましょう。)作成後、こちらも必要に応じて4xx、5xxエラーキャッシュ時間の変更などを行っておきます。
CDN認証されていることの確認
CloudFrontディストリビューション作成、利用可能になったら実際にCDN認証されていることを確認してみます。MediaLiveで出力先にMediaStoreを指定し、ライブ視聴が可能な状況としました。
まずは先ほど認証用の設定を行ったCloudFrontディストリビューション経由でアクセスしてみます。MediaStore上のm3u8ファイルのアドレス、Endpointは以下になります。
https://ckexxxxxxxxxxx.data.mediastore.ap-northeast-1.amazonaws.com/mediastore-cdnauthtest/hls.m3u8
ドメイン名をCloudFrontディストリビューションのものに置き換えて、以下でアクセスしてみます。
https://d3cxxxxxxxxxxx.cloudfront.net/mediastore-cdnauthtest/hls.m3u8
視聴確認にはVideoJS HTTP Streamingを使用しました。Video URLにCloudFrontドメイン名のURLを入力し、MimeType:application/x-mpegURLを確認して、[Load]ボタンを押します。問題なく再生できますね!
続いて、MediaStoreのエンドポイント(オリジン)に直接アクセスしてみましょう。以下のように403エラーとなってしまいました。オリジンへの直接アクセスが無効となっていることがわかります。
これらの内容を、curlコマンドを使って確認してみます。まずは認証設定を行ったCloudFrontからのアクセスです。m3u8ファイル、tsファイルとも、問題なくアクセスできていることが確認できます。
# トップレベルマニフェストファイル [shimizu.toshiya@classmethod] [~] $ curl https://d3cxxxxxxxxxxx.cloudfront.net/mediastore-cdnauthtest/hls.m3u8 #EXTM3U #EXT-X-VERSION:3 #EXT-X-INDEPENDENT-SEGMENTS #EXT-X-STREAM-INF:BANDWIDTH=3879928,AVERAGE-BANDWIDTH=3735600,CODECS="avc1.640029,mp4a.40.2",RESOLUTION=1280x720,FRAME-RATE=29.970 hls_720p.m3u8 [shimizu.toshiya@classmethod] [~] $ curl -I https://d3cxxxxxxxxxxx.cloudfront.net/mediastore-cdnauthtest/hls.m3u8 HTTP/2 200 content-type: application/vnd.apple.mpegurl content-length: 198 x-amzn-requestid: LZNH35OJBX4YMDUSCL6CWXTAXNVU5HUEUHBCFDA6HNRJKRO5BOVADDCWZAXR66QMX2KHMLDEUC33HI7TKYJOGBY last-modified: Wed, 29 Jan 2020 12:11:53 GMT cache-control: max-age=3 etag: 11b698658a7ae847f72daa34f5839aa7464d4cb43dcf30a6b8edbb1d00c374ec date: Wed, 29 Jan 2020 12:19:15 GMT x-cache: Miss from cloudfront via: 1.1 a5b5c04f6614f3f4049a1ce34ea76c9a.cloudfront.net (CloudFront) x-amz-cf-pop: NRT57-C4 x-amz-cf-id: 1n3Haw-BJy1Ql4RRQKkv0Is7nl96NPXY0B08RbVBRE7cJmRG3D_YUA== # ビットレートごとのマニフェストファイル(今回は1つのビットレートのみです) [shimizu.toshiya@classmethod] [~] $ curl https://d3cxxxxxxxxxxx.cloudfront.net/mediastore-cdnauthtest/hls_720p.m3u8 #EXTM3U #EXT-X-VERSION:3 #EXT-X-TARGETDURATION:7 #EXT-X-MEDIA-SEQUENCE:72 #EXT-X-PROGRAM-DATE-TIME:2020-01-29T12:18:39.860Z #EXTINF:6.00600, hls_720p_00072.ts #EXTINF:6.00600, hls_720p_00073.ts #EXTINF:6.00600, hls_720p_00074.ts #EXTINF:6.00600, hls_720p_00075.ts #EXTINF:6.00600, hls_720p_00076.ts #EXTINF:6.00600, hls_720p_00077.ts #EXTINF:6.00600, hls_720p_00078.ts #EXTINF:6.00600, hls_720p_00079.ts #EXTINF:6.00600, hls_720p_00080.ts #EXTINF:6.00600, hls_720p_00081.ts [shimizu.toshiya@classmethod] [~] $ curl -I https://d3cxxxxxxxxxxx.cloudfront.net/mediastore-cdnauthtest/hls_720p.m3u8 HTTP/2 200 content-type: application/vnd.apple.mpegurl content-length: 474 x-amzn-requestid: V54NWUDWTVDJQLYQKKM3K3PYQW4CKHCR3XIJLBYPI4FWH6NIKQPIXHIXULO2HVHYYGIMOQX3RILOESEYB7TTBWQ last-modified: Wed, 29 Jan 2020 12:19:53 GMT cache-control: max-age=3 etag: c2c605f332b3056b1f306014628a0dd173ea8867ec8cc58a7ef64f8eaabcd8e9 date: Wed, 29 Jan 2020 12:19:53 GMT x-cache: Miss from cloudfront via: 1.1 6b5ed72af06c392d3a24305474d937d8.cloudfront.net (CloudFront) x-amz-cf-pop: NRT57-C4 x-amz-cf-id: Kv2P4TFXtpkHqEUx8KMX9WTPoi_AuCJQLLzzmudbLpNWC2j0C4IS2Q== # tsファイル [shimizu.toshiya@classmethod] [~] $ curl -I https://d3cxxxxxxxxxxx.cloudfront.net/mediastore-cdnauthtest/hls_720p_00081.ts HTTP/2 200 content-type: video/MP2T content-length: 2605868 x-amzn-requestid: WO7AJUEWJUGFQ3DZEIGQQE7EWFTOI3EZIMCNCTE6VTUXKET672NJTYOO2F27IIZY75TYRPXIRHQYQSNXFWU2PLA last-modified: Wed, 29 Jan 2020 12:19:41 GMT cache-control: max-age=21600 etag: e29e3f7ee59e3689d19e09fb272aa64e504b8182406d3f399979008a8b2005a7 date: Wed, 29 Jan 2020 12:20:11 GMT x-cache: Miss from cloudfront via: 1.1 db3d90fd7e6c6a16b47e88be13e9768c.cloudfront.net (CloudFront) x-amz-cf-pop: NRT57-C4 x-amz-cf-id: lt6tfCGcrqzUnzGata6QZaEDDer1q98943FMG0kWqaynRgQW8Pjm2g==
続いてMediaStoreのエンドポイントに直接アクセスしてみます。以下のように、403エラーでアクセスできないことがわかります。
# トップレベルマニフェストファイル [shimizu.toshiya@classmethod] [~] $ curl https://ckexxxxxxxxxxx.data.mediastore.ap-northeast-1.amazonaws.com/mediastore-cdnauthtest/hls.m3u8 {"Message":"Access Denied."} [shimizu.toshiya@classmethod] [~] $ curl -I https://ckexxxxxxxxxxx.data.mediastore.ap-northeast-1.amazonaws.com/mediastore-cdnauthtest/hls.m3u8 HTTP/1.1 403 x-amzn-RequestId: LLG6ZRXYN2NX734OONOINANHKWM4TIOCVGNWBLM4GVMUGPAO2EPCHHA3JOXXWYTFUIAN6HVIUZTXX6A45JOYK6Y x-amzn-ErrorType: AccessDeniedException Content-Type: application/x-amz-json-1.1 Transfer-Encoding: chunked Date: Wed, 29 Jan 2020 12:20:33 GMT # ビットレートごとのマニフェストファイル(今回は1つのビットレートのみです) [shimizu.toshiya@classmethod] [~] $ curl https://ckexxxxxxxxxxx.data.mediastore.ap-northeast-1.amazonaws.com/mediastore-cdnauthtest/hls_720p.m3u8 {"Message":"Access Denied."} [shimizu.toshiya@classmethod] [~] $ curl -I https://ckexxxxxxxxxxx.data.mediastore.ap-northeast-1.amazonaws.com/mediastore-cdnauthtest/hls_720p.m3u8 HTTP/1.1 403 x-amzn-RequestId: GGZRXOAP7ODLFPMFWQQLJXNUCAAKZHMCON3CBXZBXRY4TTUPXTWZ5NNA7UD4U72G46BXVWCEMTWTUH2XYB4BELQ x-amzn-ErrorType: AccessDeniedException Content-Type: application/x-amz-json-1.1 Transfer-Encoding: chunked Date: Wed, 29 Jan 2020 12:20:51 GMT # tsファイル(MediaStore上でファイルが存在していることは確認済みです) [shimizu.toshiya@classmethod] [~] $ curl -I https://ckexxxxxxxxxxx.data.mediastore.ap-northeast-1.amazonaws.com/mediastore-cdnauthtest/hls_720p_00081.ts HTTP/1.1 403 x-amzn-RequestId: DUFVUXPG72B5G44EL3Q3UG4NL26H5VCLYID3GNJIW4TANNY5NDH73S3MG6UUPMFMEF6NZALCGUGNJDD75DKILUI x-amzn-ErrorType: AccessDeniedException Content-Type: application/x-amz-json-1.1 Transfer-Encoding: chunked Date: Wed, 29 Jan 2020 12:21:10 GMT
続いてCloudFrontディストリビューションに設定したように、認証情報となるUser Agentヘッダを個別の情報(今回なら認証コード「995c728c-d39b-4c1c-bab6-724a77795b9d」としてアクセスを確認してみましょう。curlコマンドにオプションで、ヘッダ名: User-Agent、ヘッダの値: 995c728c-d39b-4c1c-bab6-724a77795b9d(認証コード)を付与してアクセスしてみます。結果としては以下の通り、問題なくアクセスできることが確認できます。
# トップレベルマニフェストファイル [shimizu.toshiya@classmethod] [~] $ curl -H 'User-Agent:995c728c-d39b-4c1c-bab6-724a77795b9d' https://ckexxxxxxxxxxx.data.mediastore.ap-northeast-1.amazonaws.com/mediastore-cdnauthtest/hls.m3u8 #EXTM3U #EXT-X-VERSION:3 #EXT-X-INDEPENDENT-SEGMENTS #EXT-X-STREAM-INF:BANDWIDTH=3879928,AVERAGE-BANDWIDTH=3735600,CODECS="avc1.640029,mp4a.40.2",RESOLUTION=1280x720,FRAME-RATE=29.970 hls_720p.m3u8 [shimizu.toshiya@classmethod] [~] $ curl -I -H 'User-Agent:995c728c-d39b-4c1c-bab6-724a77795b9d' https://ckexxxxxxxxxxx.data.mediastore.ap-northeast-1.amazonaws.com/mediastore-cdnauthtest/hls.m3u8 HTTP/1.1 200 x-amzn-RequestId: B3UHI2GYMCCAIZNB65HLTPUPFEW3YEXX2YNRUBCL2BJJ62T577UL66VLLHOIKLLCSAF32ASO4UO77LQSDPS3RPQ Last-Modified: Wed, 29 Jan 2020 12:11:53 GMT Cache-Control: max-age=3 ETag: 11b698658a7ae847f72daa34f5839aa7464d4cb43dcf30a6b8edbb1d00c374ec Content-Type: application/vnd.apple.mpegurl Content-Length: 198 Date: Wed, 29 Jan 2020 12:22:45 GMT # ビットレートごとのマニフェストファイル(今回は1つのビットレートのみです) [shimizu.toshiya@classmethod] [~] $ curl -H 'User-Agent:995c728c-d39b-4c1c-bab6-724a77795b9d' https://ckexxxxxxxxxxx.data.mediastore.ap-northeast-1.amazonaws.com/mediastore-cdnauthtest/hls_720p.m3u8 #EXTM3U #EXT-X-VERSION:3 #EXT-X-TARGETDURATION:7 #EXT-X-MEDIA-SEQUENCE:104 #EXT-X-PROGRAM-DATE-TIME:2020-01-29T12:21:52.052Z #EXTINF:6.00600, hls_720p_00104.ts #EXTINF:6.00600, hls_720p_00105.ts #EXTINF:6.00600, hls_720p_00106.ts #EXTINF:6.00600, hls_720p_00107.ts #EXTINF:6.00600, hls_720p_00108.ts #EXTINF:6.00600, hls_720p_00109.ts #EXTINF:6.00600, hls_720p_00110.ts #EXTINF:6.00600, hls_720p_00111.ts #EXTINF:6.00600, hls_720p_00112.ts #EXTINF:6.00600, hls_720p_00113.ts [shimizu.toshiya@classmethod] [~] $ curl -I -H 'User-Agent:995c728c-d39b-4c1c-bab6-724a77795b9d' https://ckexxxxxxxxxxx.data.mediastore.ap-northeast-1.amazonaws.com/mediastore-cdnauthtest/hls_720p.m3u8 HTTP/1.1 200 x-amzn-RequestId: UUOOWL3OZ5WCCSSWG7FIMOCBVEWNBEZOBLPELW2AWF67WTH6NWGEEIU5KZSMNIP6UUTLCDA7U24PUJI4GMYH3TQ Last-Modified: Wed, 29 Jan 2020 12:22:53 GMT Cache-Control: max-age=3 ETag: 2b01e6bebba3b0ba31cd8e98b16d0f48af2481707fff0e5693a938400e425049 Content-Type: application/vnd.apple.mpegurl Content-Length: 475 Date: Wed, 29 Jan 2020 12:22:57 GMT # tsファイル [shimizu.toshiya@classmethod] [~] $ curl -I -H 'User-Agent:995c728c-d39b-4c1c-bab6-724a77795b9d' https://ckexxxxxxxxxxx.data.mediastore.ap-northeast-1.amazonaws.com/mediastore-cdnauthtest/hls_720p_00113.ts HTTP/1.1 200 x-amzn-RequestId: ZN6IBF6LNUKCOJINVRGMYEY3T5455GWOAXH53MG6B7HTXKN2KTYUNNOJPJNSI7UR6LIRK3IGBUPK6VM5TASOQBY Last-Modified: Wed, 29 Jan 2020 12:22:53 GMT Cache-Control: max-age=21600 ETag: 94852ef8e43d61c2feb533e4971283b643551e1817ee85704c63a8476153101e Content-Type: video/MP2T Content-Length: 2632188 Date: Wed, 29 Jan 2020 12:23:32 GMT [shimizu.toshiya@classmethod] [~] $ curl -I -H 'User-Agent:995c728c-d39b-4c1c-bab6-724a77795b9d' https://ckexxxxxxxxxxx.data.mediastore.ap-northeast-1.amazonaws.com/mediastore-cdnauthtest/hls_720p_00113.ts HTTP/1.1 200 x-amzn-RequestId: 6FLA22VAX57DHMCHOENOTJGZZXVKAOCWAP5AIGC5ROM4H3FH3GBXLEFIPMI7Z73YQZ4WVVBTCEN34EEP2KMO73Q Last-Modified: Wed, 29 Jan 2020 12:22:53 GMT Cache-Control: max-age=21600 ETag: 94852ef8e43d61c2feb533e4971283b643551e1817ee85704c63a8476153101e Content-Type: video/MP2T Content-Length: 2632188 Date: Wed, 29 Jan 2020 12:23:50 GMT
まとめ
メディア向けに最適化されたストレージサービスでありAmazon S3よりも整合性の強いストレージでもあるAWS Elemental MediaStoreで、特定のUser Agentの場合にのみアクセス許可するようコンテナポリシーを設定することでCDN認証を実現し、ライブ配信時のオリジン保護をしてみました。AWS Elemental MediaPackageでのCDN認証と比較すると、やはりいち機能として設定するのではなく、コンテナポリシーの中で条件として設定するのが大きく異なるポイントかと思います。細かく言えば、AWS Secrets Managerを使うか使わないかもポイントですね。ただCDNとなるAmazon CloudFront側は、ヘッダさえ違えど、設定内容は同様かと考えます。
ということで先日のアップデート、AWS Elemental MediaPackageでのCDN認証によるオリジン保護はAWS Elemental MediaStoreでも同様のことができることがわかりました。ライブ配信時のオリジン選定について、オリジン保護の観点ではどちらも利用することができますので、その他の要件にあわせてどちらをオリジンとして使うか検討してみましょう。